// Copyright 2021 The XLS Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Tests that explicit check the IR output generated by various DSL constructs.
//
// This amounts to whitebox testing of the IR converter end-to-end, whereas DSLX
// tests (i.e. in dslx/tests) are testing functional correctness of results
// (which is more blackbox with respect to the IR conversion process).

#include <optional>
#include <string>
#include <string_view>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/status/statusor.h"
#include "xls/common/file/temp_file.h"
#include "xls/common/status/matchers.h"
#include "xls/common/status/status_macros.h"
#include "xls/dslx/create_import_data.h"
#include "xls/dslx/import_data.h"
#include "xls/dslx/ir_convert/conversion_info.h"
#include "xls/dslx/ir_convert/convert_options.h"
#include "xls/dslx/ir_convert/ir_converter.h"
#include "xls/dslx/ir_convert/test_utils.h"
#include "xls/dslx/parse_and_typecheck.h"
#include "xls/dslx/run_routines/run_comparator.h"
#include "xls/dslx/run_routines/run_routines.h"
#include "xls/dslx/type_system/typecheck_test_utils.h"

namespace xls::dslx {
namespace {

using ::absl_testing::StatusIs;
using ::testing::HasSubstr;

constexpr std::string_view kProgramToVerifyTestConversion = R"(
#[cfg(test)]
fn test_utility_function() { trace_fmt!("Message from a test utility function"); }

fn normal_function() { trace_fmt!("Message from a normal function"); }

#[test]
fn test_func() {
    test_utility_function();
    normal_function();
    trace_fmt!("Message from a test function");
}

#[cfg(test)]
proc TestUtilityProc {
    req_r: chan<()> in;
    resp_s: chan<()> out;

    config(req_r: chan<()> in, resp_s: chan<()> out) { (req_r, resp_s) }

    init {  }

    next(state: ()) {
        let (tok, _) = recv(join(), req_r);
        trace_fmt!("Message from a TestUtilityProc");
        send(tok, resp_s, ());
    }
}

proc NormalProc {
    req_r: chan<()> in;
    resp_s: chan<()> out;

    config(req_r: chan<()> in, resp_s: chan<()> out) { (req_r, resp_s) }

    init {  }

    next(state: ()) {
        let (tok, _) = recv(join(), req_r);
        trace_fmt!("Message from a NormalProc");
        send(tok, resp_s, ());
    }
}

#[test_proc]
proc TestProc {
    terminator: chan<bool> out;
    tester_req_s: chan<()> out;
    tester_resp_r: chan<()> in;
    user_req_s: chan<()> out;
    user_resp_r: chan<()> in;

    config(terminator: chan<bool> out) {
        let (tester_req_s, tester_req_r) = chan<()>("tester_req");
        let (tester_resp_s, tester_resp_r) = chan<()>("tester_resp");

        spawn TestUtilityProc(tester_req_r, tester_resp_s);

        let (user_req_s, user_req_r) = chan<()>("user_req");
        let (user_resp_s, user_resp_r) = chan<()>("user_resp");

        spawn NormalProc(user_req_r, user_resp_s);

        (terminator, tester_req_s, tester_resp_r, user_req_s, user_resp_r)
    }

    init {  }

    next(state: ()) {
        let tok = send(join(), tester_req_s, ());
        let (tok, _) = recv(join(), tester_resp_r);

        let tok = send(join(), user_req_s, ());
        let (tok, _) = recv(join(), user_resp_r);

        trace_fmt!("Message from a TestProc");

        send(tok, terminator, true);
    }
}
)";

absl::StatusOr<TestResultData> ParseAndTest(
    std::string_view program, std::string_view module_name,
    std::string_view filename, const ParseAndTestOptions& options) {
  // Other interpreters rely on ir_convert so we can't test with them.
  return DslxInterpreterTestRunner().ParseAndTest(program, module_name,
                                                  filename, options);
}

constexpr ConvertOptions kNoPosOptions = {
    .emit_positions = false,
    .lower_to_proc_scoped_channels = false,
};

constexpr ConvertOptions kProcScopedChannelOptions = {
    .emit_positions = false,
    .lower_to_proc_scoped_channels = true,
};

void ExpectIr(std::string_view got) {
  ::xls::dslx::ExpectIr(got, TestName(), "ir_converter_legacy_test");
}

absl::StatusOr<std::string> ConvertOneFunctionForTest(
    std::string_view program, std::string_view fn_name, ImportData& import_data,
    const ConvertOptions& options,
    std::optional<TypeInferenceVersion> force_typecheck_version =
        std::nullopt) {
  XLS_ASSIGN_OR_RETURN(
      TypecheckedModule tm,
      ParseAndTypecheck(program, /*path=*/"test_module.x",
                        /*module_name=*/"test_module", &import_data,
                        /*comments=*/nullptr, force_typecheck_version));
  return ConvertOneFunction(tm.module, /*entry_function_name=*/fn_name,
                            &import_data,
                            /*parametric_env=*/nullptr, options);
}

absl::StatusOr<std::string> ConvertOneFunctionForTest(
    std::string_view program, std::string_view fn_name,
    const ConvertOptions& options = kNoPosOptions,
    std::optional<TypeInferenceVersion> force_typecheck_version =
        std::nullopt) {
  auto import_data = CreateImportDataForTest();
  return ConvertOneFunctionForTest(program, fn_name, import_data, options,
                                   force_typecheck_version);
}

absl::StatusOr<std::string> ConvertModuleForTest(
    std::string_view program, const ConvertOptions& options = kNoPosOptions,
    ImportData* import_data = nullptr,
    std::optional<TypeInferenceVersion> force_typecheck_version =
        std::nullopt) {
  std::optional<ImportData> import_data_value;
  if (import_data == nullptr) {
    import_data_value.emplace(CreateImportDataForTest());
    import_data = &*import_data_value;
  }
  XLS_ASSIGN_OR_RETURN(
      TypecheckedModule tm,
      ParseAndTypecheck(program, "test_module.x", "test_module", import_data,
                        /*comments=*/nullptr, force_typecheck_version,
                        options));
  XLS_ASSIGN_OR_RETURN(std::string converted,
                       ConvertModule(tm.module, import_data, options));
  return converted;
}

class IrConverterLegacyTest : public ::testing::Test {
 public:
  absl::StatusOr<std::string> ConvertOneFunctionForTest(
      std::string_view program, std::string_view fn_name,
      ImportData& import_data, const ConvertOptions& options) {
    return ::xls::dslx::ConvertOneFunctionForTest(
        program, fn_name, import_data, options,
        TypeInferenceVersion::kVersion2);
  }

  absl::StatusOr<std::string> ConvertOneFunctionForTest(
      std::string_view program, std::string_view fn_name,
      const ConvertOptions& options = kNoPosOptions) {
    return ::xls::dslx::ConvertOneFunctionForTest(
        program, fn_name, options, TypeInferenceVersion::kVersion2);
  }

  absl::StatusOr<std::string> ConvertModuleForTest(
      std::string_view program, const ConvertOptions& options = kNoPosOptions,
      ImportData* import_data = nullptr) {
    return ::xls::dslx::ConvertModuleForTest(program, options, import_data,
                                             TypeInferenceVersion::kVersion2);
  }

  absl::StatusOr<TypecheckedModule> ParseAndTypecheck(
      std::string_view program, std::string_view path,
      std::string_view module_name, ImportData* import_data = nullptr) {
    return ::xls::dslx::ParseAndTypecheck(
        program, path, module_name, import_data,
        /*comments=*/nullptr, TypeInferenceVersion::kVersion2);
  }
};

TEST_F(IrConverterLegacyTest, NamedConstant) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let foo: u32 = u32:42;
  foo
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertOneFunctionForTest(program, "f"));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, Concat) {
  constexpr std::string_view program =
      R"(fn f(x: bits[31]) -> u32 {
  bits[1]:1 ++ x
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertOneFunctionForTest(program, "f"));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ParameterNamePreservedOnLetAlias) {
  constexpr std::string_view program = R"(
pub fn my_fun(baz: u32) -> u32 {
  let foo = baz;
  foo
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "my_fun", kNoPosOptions));

  // Expect the parameter to retain its original DSLX name `baz` in IR.
  EXPECT_EQ(converted, R"(package test_module

file_number 0 "test_module.x"

top fn __test_module__my_fun(baz: bits[32] id=1) -> bits[32] {
  ret baz: bits[32] = param(name=baz, id=1)
}
)");
}

TEST_F(IrConverterLegacyTest, TwoPlusTwo) {
  constexpr std::string_view program =
      R"(fn two_plus_two() -> u32 {
  u32:2 + u32:2
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertOneFunctionForTest(program, "two_plus_two"));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, SignedDiv) {
  constexpr std::string_view program =
      R"(fn signed_div(x: s32, y: s32) -> s32 {
  x / y
})";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertOneFunctionForTest(program, "signed_div"));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, NegativeX) {
  constexpr std::string_view program =
      R"(fn negate(x: u32) -> u32 {
  -x
})";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertOneFunctionForTest(program, "negate"));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, LetBinding) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let x: u32 = u32:2;
  x+x
})";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertOneFunctionForTest(program, "f"));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, LetTupleBinding) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let t = (u32:2, u32:3);
  let (x, y) = t;
  x+y
})";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertOneFunctionForTest(program, "f"));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, LetTupleBindingNested) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let t = (u32:2, (u32:3, (u32:4,), u32:5));
  let (x, (y, (z,), a)) = t;
  x+y+z+a
})";
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "f", kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, LetTupleBindingRestOfTupleNestedNone) {
  constexpr std::string_view program =
      R"(
#[test]
fn f() {
  let t = (u32:1, (u32:2, (u32:3,), u32:4));
  let (x, .., (y, .., (z, ..), a)) = t;
  assert_eq(x+y+z+a, u32:10)
})";
  RunComparator run_comparator(CompareMode::kInterpreter);
  XLS_ASSERT_OK(ParseAndTest(program, "", "test_module.x",
                             ParseAndTestOptions{
                                 .run_comparator = &run_comparator,
                             }));
}

TEST_F(IrConverterLegacyTest, LetTupleBindingRestOfTupleNested) {
  constexpr std::string_view program =
      R"(
#[test]
fn f() {
  let t = (u32:1, u32:2, u32:3, (u32:4, u32:5, (u32:6, u32:7), u32:5));
  let (x, .., (.., y, (z, ..), a)) = t;
  assert_eq(x+y+z+a, u32:17)
})";
  RunComparator run_comparator(CompareMode::kInterpreter);
  XLS_ASSERT_OK(ParseAndTest(program, "", "test_module.x",
                             ParseAndTestOptions{
                                 .run_comparator = &run_comparator,
                             }));
}

TEST_F(IrConverterLegacyTest, LetTupleBindingWildcardNested) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let t = (u32:2, u32:3, (u32:4, u32:5, (u32:6,u32:7), u32:8));
  let (x, _, (y, _, (z, _), a)) = t;
  x+y+z+a
})";
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "f", kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, LetTupleBindingWildcard) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let t = (u32:2, u32:3);
  let (x, _) = t;
  x
})";
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "f", kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, LetTupleBindingRestOfTuple) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let t = (u32:2, u32:3, u32:4);
  let (x, ..) = t;
  x
})";
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "f", kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, LetTupleBindingRestOfTupleNone) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let t = (u32:2,);
  let (x, ..) = t;
  x
})";
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "f", kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, LetTupleBindingRestOfTupleBeginning) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let t = (u32:2, u32:3, u32:4);
  let (.., x) = t;
  x
})";
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "f", kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, LetTupleBindingRestOfTupleSkipsMiddle) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let t = (u32:1, u32:2, u32:3, u32:4);
  let (x, .., y) = t;
  x+y
})";
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "f", kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MatchRestOfTupleBeginning) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let t = (u32:1, u32:2, u32:3, u32:4);
  match t {
    (.., u32:3, y) => y,
    _ => u32:0
  }
})";
  RunComparator run_comparator(CompareMode::kInterpreter);
  XLS_ASSERT_OK(ParseAndTest(program, "", "test_module.x",
                             ParseAndTestOptions{
                                 .run_comparator = &run_comparator,
                             }));
}

TEST_F(IrConverterLegacyTest, MatchRestOfTupleMiddle) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let t = (u32:1, u32:2, u32:3, u32:4);
  match t {
    (u32:1, .., y) => y,
    _ => u32:0
  }
})";
  RunComparator run_comparator(CompareMode::kInterpreter);
  XLS_ASSERT_OK(ParseAndTest(program, "", "test_module.x",
                             ParseAndTestOptions{
                                 .run_comparator = &run_comparator,
                             }));
}

TEST_F(IrConverterLegacyTest, MatchRestOfTupleEnd) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let t = (u32:1, u32:2, u32:3, u32:4);
  match t {
    (u32:1, y, ..) => y,
    _ => u32:0
  }
})";
  RunComparator run_comparator(CompareMode::kInterpreter);
  XLS_ASSERT_OK(ParseAndTest(program, "", "test_module.x",
                             ParseAndTestOptions{
                                 .run_comparator = &run_comparator,
                             }));
}

TEST_F(IrConverterLegacyTest, MatchTupleOfTuplesRestOfTuple) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let t = (u32:1, (u32:2, u32:3, u32:4), u32:5, u32:6);
  match t {
    (u32:1, .., a) => a,
    (u32:1, (b, ..), ..) => b,
    (u32:0, (.., d), ..) => d,
    (u32:0, (e, .., g), ..) => g,
    (.., h, u32:5) => h,
    _ => u32:0
  }
})";
  RunComparator run_comparator(CompareMode::kInterpreter);
  XLS_ASSERT_OK(ParseAndTest(program, "", "test_module.x",
                             ParseAndTestOptions{
                                 .run_comparator = &run_comparator,
                             }));
}

TEST_F(IrConverterLegacyTest, MatchRestOfTupleAsTrailingArm) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let t = (u32:1, u32:2);
  match t {
    (u32:1, .., a) => a,
    (..) => u32:1
  }
})";
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "f", kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, Struct) {
  constexpr std::string_view program =
      R"(struct S {
  zub: u8,
  qux: u8,
}

fn f(a: S, b: S) -> u8 {
  let foo = a.zub + b.qux;
  (S { zub: u8:42, qux: u8:0 }).zub + (S { zub: u8:22, qux: u8:11 }).zub
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "f", kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, Index) {
  constexpr std::string_view program =
      R"(fn f(x: uN[32][4]) -> u32 {
  x[u32:0]
})";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertOneFunctionForTest(program, "f"));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, TupleOfParameters) {
  constexpr std::string_view program =
      R"(fn f(x: u8, y: u8) -> (u8, u8) {
  (x, y)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertOneFunctionForTest(program, "f"));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, TupleOfLiterals) {
  constexpr std::string_view program =
      R"(fn f() -> (u8, u8) {
  (u8:0xaa, u8:0x55)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertOneFunctionForTest(program, "f"));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, CountedFor) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  for (i, accum): (u32, u32) in u32:0..u32:4 {
    accum + i
  }(u32:0)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "f", kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ForOverArrayOfItems) {
  constexpr std::string_view program = R"(
fn main(a: (u7, u7)[3]) -> u7 {
  for (t, accum): ((u7, u7), u7) in a {
    accum + t.0 + t.1
  }(u7:0)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertOneFunctionForTest(program, "main"));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ForOverArrayLiteral) {
  constexpr std::string_view program = R"(
fn main() -> u7 {
  for (t, accum): ((u7, u7), u7) in (u7, u7)[2]:[(u7:0, u7:1), (u7:2, u7:3)] {
    accum + t.0 + t.1
  }(u7:0)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertOneFunctionForTest(program, "main"));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, CountedForDestructuring) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let t = for (i, (x, y)): (u32, (u32, u8)) in u32:0..u32:4 {
    (x + i, y)
  }((u32:0, u8:0));
  t.0
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "f", kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, CountedForParametricConst) {
  constexpr std::string_view program =
      R"(fn f<N: u32>(x: bits[N]) -> u32 {
  for (i, accum): (u32, u32) in u32:0..N {
    accum + i
  }(u32:0)
}
fn main() -> u32 {
  f(bits[2]:0)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, CountedForInvokingFunctionFromBody) {
  constexpr std::string_view program =
      R"(fn my_id(x: u32) -> u32 { x }
fn f() -> u32 {
  for (i, accum): (u32, u32) in u32:0..u32:4 {
    my_id(accum + i)
  }(u32:0)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, CountedForVariableRange) {
  constexpr std::string_view program =
      R"(fn f(x:u32) -> u32 {
  for (i, accum): (u32, u32) in u32:0..x {
    accum + i
  }(u32:0)
}
)";
  auto status_or_ir = ConvertOneFunctionForTest(program, "f", kNoPosOptions);
  ASSERT_FALSE(status_or_ir.ok());
}

TEST_F(IrConverterLegacyTest, ExtendConversions) {
  constexpr std::string_view program =
      R"(fn main(x: u8, y: s8) -> (u32, u32, s32, s32) {
  (x as u32, y as u32, x as s32, y as s32)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, TupleIndex) {
  constexpr std::string_view program =
      R"(fn main() -> u8 {
  let t = (u32:3, u8:4);
  t.1
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, BasicStruct) {
  constexpr std::string_view program =
      R"(
struct Point {
  x: u32,
  y: u32,
}

fn f(xy: u32) -> Point {
  Point { x: xy, y: xy }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, InvokeNullary) {
  constexpr std::string_view program =
      R"(fn callee() -> u32 {
  u32:42
}
fn caller() -> u32 {
  callee()
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, Match) {
  constexpr std::string_view program =
      R"(
fn f(x: u8) -> u2 {
  match x {
    u8:42 => u2:0,
    u8:64 => u2:1,
    _ => u2:2
  }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MatchDefaultOnly) {
  constexpr std::string_view program =
      R"(
fn f(x: u8) -> u2 {
  match x {
    _ => u2:2
  }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MatchDense) {
  constexpr std::string_view program =
      R"(
fn f(x: u2) -> u8 {
  match x {
    u2:0 => u8:42,
    u2:1 => u8:64,
    u2:2 => u8:128,
    _ => u8:255
  }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, EnumUse) {
  constexpr std::string_view program =
      R"(
enum Foo : u32 {
  THING = 0,
  OTHER = 1,
}
fn f(x: Foo) -> Foo {
  if x == Foo::THING { Foo::OTHER } else { Foo::THING }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ArrayEllipsis) {
  constexpr std::string_view program =
      R"(
fn main() -> u8[2] {
  u8[2]:[0, ...]
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, NonConstArrayEllipsis) {
  constexpr std::string_view program =
      R"(
fn main(x: bits[8]) -> u8[4] {
  u8[4]:[u8:0, x, ...]
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ArrayUpdate) {
  constexpr std::string_view program =
      R"(
fn main(input: u8[2]) -> u8[2] {
  update(input, u32:1, u8:0x42)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

// TODO(https://github.com/google/xls/issues/1289): Need to be able to convert
// enumerate builtin.
TEST_F(IrConverterLegacyTest, DISABLED_ArrayEnumerate) {
  constexpr std::string_view program = R"(
fn main(array: u8[4]) -> (u32, u8)[4]) {
  enumerate(array)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertOneFunctionForTest(program, "main"));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, SplatStructInstance) {
  constexpr std::string_view program =
      R"(
struct Point {
  x: u32,
  y: u32,
}

fn f(p: Point, new_y: u32) -> Point {
  Point { y: new_y, ..p }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, BoolLiterals) {
  constexpr std::string_view program =
      R"(
fn f(x: u8) -> bool {
  if x == u8:42 { true } else { false }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MatchIdentity) {
  constexpr std::string_view program =
      R"(
fn f(x: u8) -> u2 {
  match x {
    u8:42 => u2:3,
    _ => x as u2
  }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, Conditional) {
  constexpr std::string_view program =
      R"(fn main(x: bool) -> u8 {
  if x { u8:42 } else { u8:24 }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConditionalsPlusStuff) {
  constexpr std::string_view program =
      R"(
fn foo(a: bool) -> u8 {
  let x = if a { u8:42 } else { u8:24 } + u8:1;
  let y = u8:1 + if a { u8:42 } else { u8:24 };
  let z = if a { u8:42 } else { u8:24 } + if a { u8:42 } else { u8:24 };
  x + y + z
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConstantsWithConditionalsPlusStuff) {
  constexpr std::string_view program =
      R"(
const A = true;
const X = if A { u8:42 } else { u8:24 } + u8:1;
const Y = u8:1 + if A { u8:42 } else { u8:24 };
const Z = if A { u8:42 } else { u8:24 } + if A { u8:42 } else { u8:24 };
fn main(x: bool) -> u8 { X + Y + Z }
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MatchPackageLevelConstant) {
  constexpr std::string_view program =
      R"(const FOO = u8:0xff;
fn f(x: u8) -> u2 {
  match x {
    FOO => u2:0,
    _ => x as u2
  }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ParametricInvocation) {
  constexpr std::string_view program =
      R"(
fn parametric_id<N: u32>(x: bits[N]) -> bits[N] {
  x+(N as bits[N])
}

fn main(x: u8) -> u8 {
  parametric_id(x)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MatchUnderLet) {
  constexpr std::string_view program =
      R"(
fn main(x: u8) -> u8 {
  let t = match x {
    u8:42 => u8:0xff,
    _ => x
  };
  t
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, WidthSlice) {
  constexpr std::string_view program =
      R"(
fn f(x: u32, y: u32) -> u8 {
  x[2+:u8]+x[y+:u8]
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, SingleElementBitsArrayParam) {
  constexpr std::string_view program =
      R"(
fn f(x: u32[1]) -> u32[1] {
  x
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, SingleElementEnumArrayParam) {
  constexpr std::string_view program =
      R"(
enum Foo : u2 {}
fn f(x: Foo[1]) -> Foo[1] {
  x
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, BitSliceCast) {
  constexpr std::string_view program =
      R"(
fn main(x: u2) -> u1 {
  x as u1
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MatchDenseConsts) {
  constexpr std::string_view program =
      R"(
type MyU2 = u2;
const ZERO = MyU2:0;
const ONE = MyU2:1;
const TWO = MyU2:2;
fn f(x: u2) -> u8 {
  match x {
    ZERO => u8:42,
    ONE => u8:64,
    TWO => u8:128,
    _ => u8:255
  }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, CountedForWithLoopInvariants) {
  constexpr std::string_view program =
      R"(
fn f(outer_thing_1: u32, outer_thing_2: u32) -> u32 {
  let outer_thing_3: u32 = u32:42;
  let outer_thing_4: u32 = u32:24;
  for (i, accum): (u32, u32) in u32:0..u32:4 {
    accum + i + outer_thing_1 + outer_thing_2 + outer_thing_3 + outer_thing_4
  }(u32:0)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ParametricDefaultInStruct) {
  constexpr std::string_view program = R"(
struct Foo <X: u32, Y: u32 = {X + u32:1}, Z: u32 = {Y + u32:1}> {
    a: uN[X],
    b: uN[Y],
    c: uN[Z]
}

fn make_zero_foo<X: u32>() -> Foo<X> {
  zero!<Foo<X>>()
}

fn test() -> Foo<u32:5> {
 make_zero_foo<u32:5>()
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

// See https://github.com/google/xls/issues/1615
TEST_F(IrConverterLegacyTest, ParametricStructReverseOrderParametrics) {
  constexpr std::string_view program = R"(
struct Foo<X: u32, Y: u32, Z:u32 = {X}> {
    a: uN[X],
    b: uN[Y],
    c: uN[Z],
}

fn make_zero_foo<X: u32, Y: u32>() -> Foo<Y, X> {
  Foo<Y, X> { a: zero!<uN[Y]>(), b: zero!<uN[X]>(), c: zero!<uN[Y]>() }
}

fn test() -> Foo<u32:6, u32:5> {
 make_zero_foo<u32:5, u32:6>()
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

// This is an example where we use an externally-defined parametric function
// into module scope and invoke it at module scope.
// TODO: https://github.com/google/xls/issues/2876 - TIv2 does not yet
// support "use" syntax.
TEST_F(IrConverterLegacyTest,
       DISABLED_UseOfClog2InModuleScopedConstantDefinition) {
  constexpr std::string_view program = R"(#![feature(use_syntax)]
use std::clog2;

const MAX_BITS: u32 = clog2(u32:256);

fn main() -> u32 {
    MAX_BITS
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, UnrollForSimple) {
  constexpr std::string_view program = R"(
fn test() -> u32 {
  unroll_for!(i, acc): (u32, u32) in u32:0..u32:4 {
    i + acc
  }(u32:0)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, UnrollForNonU32) {
  constexpr std::string_view program = R"(
fn test() -> u8 {
  unroll_for!(i, acc): (u8, u8) in u8:0..u8:4 {
    i + acc
  }(u8:0)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, UnrollForWithSignedIterable) {
  constexpr std::string_view program = R"(
fn test() -> s32 {
  unroll_for!(i, acc): (s32, s32) in [-s32:2, s32:0, s32:5] {
    i + acc
  }(s32:0)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, UnrollForWithoutIndexName) {
  constexpr std::string_view program = R"(
fn test() -> u32 {
  unroll_for!(_, acc): (u32, u32) in u32:0..u32:4 {
    acc + u32:2
  }(u32:0)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, UnrollForWithoutAccName) {
  constexpr std::string_view program = R"(
fn test() -> u32 {
  unroll_for!(i, _): (u32, u32) in u32:0..u32:4 {
    trace_fmt!("{}", i);
    i
  }(u32:0)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, UnrollForWithoutIndexAccTypeAnnotation) {
  constexpr std::string_view program = R"(
proc SomeProc {
  init { () }
  config() { }
  next(state: ()) {
    unroll_for! (i, a) in u32:0..u32:4 {
      a + i
    }(u32:0);
  }
})";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, UnrollForNested) {
  constexpr std::string_view program = R"(
fn test() -> u32 {
  unroll_for!(i, acc): (u32, u32) in u32:0..u32:4 {
    let x = unroll_for!(j, acc2): (u32, u32) in u32:3..u32:6 {
      j + acc2
    }(u32:11);
    x + i + acc
  }((u32:0))
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, UnrollForWithArrayAsIterable) {
  constexpr std::string_view program = R"(
fn test() -> u32 {
  unroll_for!(i, acc): (u32, u32) in [u32:3, u32:4, u32:1] {
    i + acc
  }(u32:0)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, UnrollForWithNonConstexprIterable) {
  constexpr std::string_view program = R"(
fn test(x:u32, y:u32) -> u32 {
  unroll_for!(i, acc): (u32, u32) in [x, y] {
    i + acc
  }(u32:0)
}
)";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, UnrollForWithNonBitsIterable) {
  constexpr std::string_view program = R"(
fn test() -> u32 {
  unroll_for!(i, acc): ((u32, u32), u32) in [(u32:0, u32:5)] {
    i.0 + acc
  }(u32:0)
}
)";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, UnrollForWithParametric) {
  constexpr std::string_view program = R"(
fn test<SIZE:u32>() -> bits[SIZE] {
  unroll_for!(i, acc): (bits[SIZE], bits[SIZE]) in bits[SIZE]:0..bits[SIZE]:4 {
    i + acc
  }(zero!<bits[SIZE]>())
}

fn foo() -> u8 {
  (test<u32:7>() as u8) + test<u32:8>()
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, UnrollForWithTupleAccumulator) {
  constexpr std::string_view program = R"(
fn test() -> (u32, u32) {
  unroll_for!(i, (acc1, acc2)): (u32, (u32, u32)) in u32:0..u32:4 {
    (i + acc1, i + acc2)
  }((u32:2, u32:3))
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, CountedForWithTupleAccumulator) {
  constexpr std::string_view program =
      R"(
fn f() -> (u32, u32) {
  for (i, (a, b)): (u32, (u32, u32)) in u32:0..u32:4 {
    (a+b, b+u32:1)
  }((u32:0, u32:1))
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, InvokeMultipleArgs) {
  constexpr std::string_view program =
      R"(fn callee(x: bits[32], y: bits[32]) -> bits[32] {
  x + y
}
fn caller() -> u32 {
  callee(u32:2, u32:3)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, CastOfAdd) {
  constexpr std::string_view program =
      R"(
fn main(x: u8, y: u8) -> u32 {
  (x + y) as u32
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, IdentityFinalArg) {
  constexpr std::string_view program =
      R"(
fn main(x0: u19, x3: u29) -> u29 {
  let x15: u29 = u29:0;
  let x17: u19 = (x0) + (x15 as u19);
  x3
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ModuleLevelConstantDims) {
  constexpr std::string_view program =
      R"(
const BATCH_SIZE = u32:17;

fn main(x: u32[BATCH_SIZE]) -> u32 {
  x[u32:16]
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, Signex) {
  constexpr std::string_view program =
      R"(
fn main(x: u8) -> u32 {
  signex(x, u32:0)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, SMulp) {
  constexpr std::string_view program = R"(
fn main(x: s10, y: s10) -> s10 {
  let product = smulp(x, y);
  let sum = product.0 + product.1;
  sum as s10
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, UMulp) {
  constexpr std::string_view program = R"(
fn main(x: u10, y: u10) -> u10 {
  let product = umulp(x, y);
  product.0 + product.1
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, OneHotSelSplat) {
  constexpr std::string_view program =
      R"(
fn main(s: u2) -> u32 {
  one_hot_sel(s, u32[2]:[2, 3])
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, OneHotSelNonArrayNode) {
  // Tests that the cases parameter of a one_hot_sel can take a node that
  // is not an Array node, but rather a name that refers to an Array.
  //
  // See https://github.com/google/xls/issues/1303
  constexpr std::string_view program =
      R"(
fn main(s: u2) -> u32 {
  let cases = u32[2]:[2, 3];
  one_hot_sel(s, cases)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, PrioritySelSplat) {
  constexpr std::string_view program =
      R"(
fn main(s: u2) -> u32 {
  priority_sel(s, u32[2]:[u32:2, u32:3], u32:4)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, PrioritySelNonArrayNode) {
  // Tests that the cases parameter of a priority_sel can take a node that
  // is not an Array node, but rather a name that refers to an Array.
  //
  // See https://github.com/google/xls/issues/1303

  constexpr std::string_view program =
      R"(
fn main(s: u2) -> u32 {
  let cases = u32[2]:[2, 3];
  priority_sel(s, cases, u32:4)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, BitSliceSyntax) {
  constexpr std::string_view program =
      R"(
fn f(x: u4) -> u2 {
  x[:2]+x[-2:]+x[1:3]+x[-3:-1]+x[0:-2]
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, InvocationMultiSymbol) {
  constexpr std::string_view program =
      R"(fn parametric<M: u32, N: u32, R: u32 = {M + N}>(x: bits[M], y: bits[N]) -> bits[R] {
  x ++ y
}
fn main() -> u8 {
  parametric(bits[3]:0, bits[5]:1)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ArrayConcat0) {
  constexpr std::string_view program =
      R"(
fn f(in1: u32[2]) -> u32 {
  let x : u32[4] = in1 ++ in1;
  x[u32:0]
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, PackageLevelConstantArray) {
  constexpr std::string_view program =
      R"(const FOO = u8[2]:[1, 2];
fn f() -> u8[2] { FOO }
fn g() -> u8[2] { FOO }
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MatchWithlet) {
  constexpr std::string_view program =
      R"(
fn f(x: u8) -> u2 {
  match x {
    u8:42 => { let x = u2:0; x },
    u8:64 => { let x = u2:1; x },
    _ => { let x = u2:2; x }
  }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, SignexAcceptsSignedOutputType) {
  constexpr std::string_view program =
      R"(
fn main(x: u8) -> s32 {
  signex(x, s32:0)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, StructWithConstSizedArray) {
  constexpr std::string_view program =
      R"(
const THING_COUNT = u32:2;
type Foo = (
  u32[THING_COUNT]
);
fn get_thing(x: Foo, i: u32) -> u32 {
  let things: u32[THING_COUNT] = x.0;
  things[i]
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

// Tests that a simple constexpr function can be evaluated at compile time
// (which we observe at IR conversion time).
TEST_F(IrConverterLegacyTest, ConstexprFunction) {
  constexpr std::string_view program =
      R"(
const MY_CONST = u32:5;
fn constexpr_fn(arg: u32) -> u32 {
  arg * MY_CONST
}

fn f() -> u32 {
  let x = constexpr_fn(MY_CONST);
  x
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, NestedTupleSignature) {
  constexpr std::string_view program =
      R"(
    type Foo = u3;

    type MyTup = (u6, u1);

    type TupOfThings = (u1, MyTup, Foo);

    type MoreStructured = (
      TupOfThings[3],
      u3,
      u1,
    );

    type Data = (u64, u1);

    fn main(r: u9, l: u10, input: MoreStructured) -> (u9, u10, Data) {
      (u9:0, u10:0, (u64:0, u1:0))
    }
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ArrayUpdateInLoop) {
  constexpr std::string_view program =
      R"(
fn main() -> u8[2] {
  for (i, accum): (u32, u8[2]) in u32:0..u32:2 {
    update(accum, i, i as u8)
  }(u8[2]:[0, 0])
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, Identity) {
  constexpr std::string_view program =
      R"(fn main(x: u8) -> u8 {
  x
})";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

// Because the function is not instantiated, we should not observe the error at
// conversion time.
TEST_F(IrConverterLegacyTest, TypeErrorInUninstantiatedParametric) {
  constexpr std::string_view program = R"(fn f<N: u32>(x: u8) -> u8 { 42(x) })";
  absl::StatusOr<std::string> converted =
      ConvertModuleForTest(program, kNoPosOptions);
  XLS_EXPECT_OK(converted.status());
}

TEST_F(IrConverterLegacyTest, PackageLevelConstantArrayAccess) {
  constexpr std::string_view program =
      R"(
const FOO = u8[2]:[1, 2];
fn f() -> u8 { FOO[u32:0] }
fn g() -> u8 { FOO[u32:1] }
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, TransitiveParametricInvocation) {
  constexpr std::string_view program =
      R"(
fn parametric_id<N: u32>(x: bits[N]) -> bits[N] {
  x+(N as bits[N])
}
fn parametric_id_wrapper<M: u32>(x: bits[M]) -> bits[M] {
  parametric_id(x)
}
fn main(x: u8) -> u8 {
  parametric_id_wrapper(x)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ParametricIrConversion) {
  constexpr std::string_view program =
      R"(
fn parametric<N: u32>(x: bits[N]) -> u32 {
  N
}

fn main() -> u32 {
  parametric(bits[2]:0) + parametric(bits[3]:0)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, UnconditionalFail) {
  constexpr std::string_view program = R"(
fn main() -> u32 {
  fail!("failure", u32:42)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, FailInTernaryConsequent) {
  constexpr std::string_view program = R"(
fn main(x: u32) -> u32 {
  if x == u32:0 { fail!("failure", x) } else { x }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, FailInTernaryAlternate) {
  constexpr std::string_view program = R"(
fn main(x: u32) -> u32 {
  if x == u32:0 { x } else { fail!("failure", x) }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

// Fail within one arm of a match expression.
TEST_F(IrConverterLegacyTest, FailInMatch) {
  constexpr std::string_view program = R"(
fn main(x: u32) -> u32 {
  match x {
    u32:42 => fail!("failure", x),
    _ => x
  }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, FailInMatchInvocation) {
  constexpr std::string_view program = R"(
fn do_fail(x: u32) -> u32 {
  fail!("failure", x)
}

fn main(x: u32) -> u32 {
  match x {
    u32:42 => do_fail(x),
    _ => x
  }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MatchMultiFail) {
  constexpr std::string_view program = R"(
fn main(x: u32) -> u32 {
  match x {
    u32:42 => fail!("failure_0", x),
    _ => fail!("failure_1", x+u32:1)
  }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, InvokeMethodThatFails) {
  constexpr std::string_view program = R"(
fn does_fail() -> u32 {
  fail!("failure", u32:42)
}

fn main(x: u32) -> u32 {
  does_fail()
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, InvokeParametricThatFails) {
  constexpr std::string_view program = R"(
fn does_fail<N: u32>() -> bits[N] {
  fail!("failure", bits[N]:42)
}

fn main(x: u32) -> u32 {
  does_fail<u32:32>()
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, InvokeParametricThatInvokesFailing) {
  constexpr std::string_view program = R"(
fn does_fail() -> u32 {
  fail!("failure", u32:42)
}

fn calls_failing<N: u32>() -> bits[N] {
  does_fail()
}

fn main(x: u32) -> u32 {
  calls_failing<u32:32>()
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, FailInsideFor) {
  constexpr std::string_view program = R"(
fn main(x: u32) -> u32 {
  for (i, x): (u32, u32) in u32:0..u32:1 {
    fail!("failure", x)
  }(u32:0)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

// Even though the fail comes after the `for` construct, we currently prepare
// the `for` to be capable of failing, since the fallibility marking happens at
// the function scope.
TEST_F(IrConverterLegacyTest, FailOutsideFor) {
  constexpr std::string_view program = R"(
fn main(x: u32) -> u32 {
  let x = for (i, x): (u32, u32) in u32:0..u32:1 {
    x
  }(u32:0);
  fail!("failure", x)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, FailInsideForWithTupleAccum) {
  constexpr std::string_view program = R"(
fn main(x: u32) -> (u32, u32) {
  for (i, (x, y)): (u32, (u32, u32)) in u32:0..u32:1 {
    fail!("failure", (x, y))
  }((u32:0, u32:0))
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, CountedForParametricRefInBody) {
  constexpr std::string_view program =
      R"(
fn f<N:u32>(init: bits[N]) -> bits[N] {
  for (i, accum): (u32, bits[N]) in u32:0..u32:4 {
    accum as bits[N]
  }(init)
}

fn main() -> u32 {
  f(u32:0)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, SignedComparisonsViaSignedNumbers) {
  constexpr std::string_view program =
      R"(
fn main(x: s32, y: s32) -> bool {
  x > y && x < y && x >= y && x <= y
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

// Tests that a parametric constexpr function can be evaluated at compile time
// (IR conversion time).
TEST_F(IrConverterLegacyTest, ParametricConstexprFn) {
  constexpr std::string_view program =
      R"(
pub const MY_CONST = u32:5;
fn constexpr_fn<N:u32>(arg: bits[N]) -> bits[N] {
  arg * MY_CONST
}

fn f() -> u32 {
  let x = constexpr_fn(MY_CONST);
  x
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConstexprImport) {
  // Place the *imported* module into the import cache.
  auto import_data = CreateImportDataForTest();
  constexpr std::string_view imported_program = R"(
import std;

pub const MY_CONST = bits[32]:5;
pub const MY_OTHER_CONST = std::clog2(MY_CONST);

pub fn constexpr_fn(arg: u32) -> u32 {
  arg * MY_CONST
}
)";
  XLS_ASSERT_OK(ParseAndTypecheck(imported_program, "fake/imported/stuff.x",
                                  "fake.imported.stuff", &import_data));
  constexpr std::string_view importer_program = R"(
import fake.imported.stuff;

fn f() -> u32 {
  let x = stuff::constexpr_fn(stuff::MY_OTHER_CONST);
  x
}
)";

  // Convert the *importer* module to IR.
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(importer_program, kNoPosOptions, &import_data));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ZeroMacroImportedStructInProcInit) {
  ImportData import_data = CreateImportDataForTest();

  constexpr std::string_view imported = R"(
pub struct S { field: u32 }
  )";
  XLS_EXPECT_OK(
      ParseAndTypecheck(imported, "imported.x", "imported", &import_data));

  constexpr std::string_view program = R"(
import imported;

proc main {
  init {
    // IR converter cares whether `imported::S` has a `MetaType` here, only for
    // macro invocations inside a proc init.
    zero!<imported::S>()
  }
  config() {
    ()
  }
  next(s: imported::S) {
    s
  }
}
  )";
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(program, kNoPosOptions, &import_data));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MapImportedFunction) {
  auto import_data = CreateImportDataForTest();
  constexpr std::string_view imported_program = R"(
pub fn some_function(x: u32) -> u32 { x }
)";
  XLS_EXPECT_OK(ParseAndTypecheck(imported_program, "fake/imported/stuff.x",
                                  "fake.imported.stuff", &import_data));
  constexpr std::string_view importer_program = R"(
import fake.imported.stuff;

fn main() -> u32[2] {
  map([u32:1, u32:2], stuff::some_function)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(importer_program, kNoPosOptions, &import_data));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MapImportedParametricFunction) {
  auto import_data = CreateImportDataForTest();
  constexpr std::string_view imported_program = R"(
pub fn some_function<N: u32>(x: uN[N]) -> uN[N] { uN[N]:0 }
)";
  XLS_EXPECT_OK(ParseAndTypecheck(imported_program, "fake/imported/stuff.x",
                                  "fake.imported.stuff", &import_data));
  constexpr std::string_view importer_program = R"(
import fake.imported.stuff;

fn main() -> u4[2] {
  map([u4:1, u4:2], stuff::some_function<u32:4>)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(importer_program, kNoPosOptions, &import_data));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ImportedParametricFnWithDefault) {
  auto import_data = CreateImportDataForTest();
  constexpr std::string_view imported_program = R"(
pub fn some_function<N: u32, M: u32 = {N + u32:1}>() -> uN[M] { uN[M]:0 }
)";
  XLS_EXPECT_OK(ParseAndTypecheck(imported_program, "fake/imported/stuff.x",
                                  "fake.imported.stuff", &import_data));
  constexpr std::string_view importer_program = R"(
import fake.imported.stuff;

fn main() -> u5 {
  let var = stuff::some_function<u32:4>();
  var
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(importer_program, kNoPosOptions, &import_data));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, TwoLevelsOfParametricCallsAcrossImportBoundary) {
  // In this example, TIv2 will create a `ParametricEnv` for the cross-module
  // baz->foo call, and that should be the caller env for the internal foo->bar
  // call. If that caller env gets incorrectly siloed in state for the
  // "imported" module that creates it, IR conversion will fail.

  auto import_data = CreateImportDataForTest();
  constexpr std::string_view imported = R"(
fn bar<N: u32>(a: uN[N]) -> uN[N] { a }

pub fn foo<A: u32>(a: uN[A]) -> uN[A] { bar(a) }

)";
  XLS_EXPECT_OK(ParseAndTypecheck(imported, "fake/imported/stuff.x",
                                  "fake.imported.stuff", &import_data));
  constexpr std::string_view importer = R"(
import fake.imported.stuff as imported;

fn baz<N: u32>(a: uN[N]) -> uN[N] { imported::foo(a) }

fn main() {
  baz(u32:5);
  baz(u24:6);
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(importer, kNoPosOptions, &import_data));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ImportConstantArray) {
  auto import_data = CreateImportDataForTest();
  constexpr std::string_view imported_program = R"(
const SIZE = u32:5;
pub const ARRAY = u32[SIZE]:[1, 2, 3, 4, 5];
)";
  XLS_EXPECT_OK(ParseAndTypecheck(imported_program, "fake/imported/stuff.x",
                                  "fake.imported.stuff", &import_data));
  constexpr std::string_view importer_program = R"(
import fake.imported.stuff;

fn main() -> u32[5] {
  let var = stuff::ARRAY;
  var
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(importer_program, kNoPosOptions, &import_data));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ImportStruct) {
  auto import_data = CreateImportDataForTest();
  constexpr std::string_view imported_program = R"(
pub struct S { x: u5[2] }
)";
  XLS_EXPECT_OK(ParseAndTypecheck(imported_program, "fake/imported/stuff.x",
                                  "fake.imported.stuff", &import_data));
  constexpr std::string_view importer_program = R"(
import fake.imported.stuff;

fn main() -> u5 {
  let s = stuff::S{x: [u5:1, u5:2]};
  s.x[u1:1]
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(importer_program, kNoPosOptions, &import_data));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ImportStructImpl) {
  auto import_data = CreateImportDataForTest();
  constexpr std::string_view imported_program = R"(
pub struct S { x: u5 }

impl S {
  fn X(self) -> u5 { self.x }
}
)";
  XLS_EXPECT_OK(ParseAndTypecheck(imported_program, "fake/imported/stuff.x",
                                  "fake.imported.stuff", &import_data));
  constexpr std::string_view importer_program = R"(
import fake.imported.stuff;

fn main() -> u5 {
  let s = stuff::S{x: u5:1};
  s.X()
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(importer_program, kNoPosOptions, &import_data));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ChainOfImports) {
  auto import_data = CreateImportDataForTest();
  constexpr std::string_view first_imported_program = R"(
pub const SOME_CONSTANT = u32:1;
)";
  XLS_EXPECT_OK(ParseAndTypecheck(first_imported_program,
                                  "fake/imported/first.x",
                                  "fake.imported.first", &import_data));

  constexpr std::string_view second_imported_program = R"(
import fake.imported.first;

pub fn get_const() -> u32 {
  first::SOME_CONSTANT
}
)";
  XLS_EXPECT_OK(ParseAndTypecheck(second_imported_program,
                                  "fake/imported/second.x",
                                  "fake.imported.second", &import_data));

  constexpr std::string_view importer_program = R"(
import fake.imported.second;

fn main() -> u1 {
  uN[second::get_const()]:0
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(importer_program, kNoPosOptions, &import_data));
  ExpectIr(converted);
}

// Tests that a parametric constexpr function can be imported.
TEST_F(IrConverterLegacyTest, ParametricConstexprImport) {
  // Place the *imported* module into the import cache.
  auto import_data = CreateImportDataForTest();
  constexpr std::string_view imported_program = R"(
pub const MY_CONST = bits[32]:5;

pub fn constexpr_fn<N:u32>(arg: bits[N]) -> bits[N] {
  arg * MY_CONST
}

)";
  XLS_ASSERT_OK(ParseAndTypecheck(imported_program, "fake/imported/stuff.x",
                                  "fake.imported.stuff", &import_data));
  constexpr std::string_view importer_program = R"(
import fake.imported.stuff;

fn f() -> u32 {
  let x = stuff::constexpr_fn(stuff::MY_CONST);
  x
}
)";

  // Convert the *importer* module to IR.
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(importer_program, kNoPosOptions, &import_data));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, BitSliceUpdate) {
  constexpr std::string_view program =
      R"(
fn main(x: u32, y: u16, z: u8) -> u32 {
  bit_slice_update(x, y, z)
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, TokenIdentityFunction) {
  constexpr std::string_view program = "fn main(x: token) -> token { x }";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ImportEnumValue) {
  auto import_data = CreateImportDataForTest();

  constexpr std::string_view import_module = R"(
import std;

pub const MY_CONST = u32:5;
pub enum ImportEnum : u16 {
  SINGLE_MY_CONST = MY_CONST as u16,
  DOUBLE_MY_CONST = std::clog2(MY_CONST) as u16 * u16:2,
  TRIPLE_MY_CONST = (MY_CONST * u32:3) as u16,
}
)";
  XLS_ASSERT_OK(ParseAndTypecheck(import_module, "fake/imported/stuff.x",
                                  "fake.imported.stuff", &import_data));

  constexpr std::string_view importer_module = R"(
import fake.imported.stuff;

type ImportedEnum = stuff::ImportEnum;

fn main(x: u32) -> u32 {
  stuff::ImportEnum::TRIPLE_MY_CONST as u32 +
      (ImportedEnum::DOUBLE_MY_CONST as u32) +
      (stuff::ImportEnum::SINGLE_MY_CONST as u32)
})";

  // Convert the importer module to IR.
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(importer_module, kNoPosOptions, &import_data));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConvertOneFunctionWithImport) {
  auto import_data = CreateImportDataForTest();
  constexpr std::string_view import_module = R"(
pub fn a() -> u32 {
  u32:42
}
)";
  XLS_ASSERT_OK(
      ParseAndTypecheck(import_module, "a.x", "a", &import_data).status());

  constexpr std::string_view importer_module = R"(
import a;

fn main(x: u32) -> u32 {
  a::a()
})";

  // Convert the importer module to IR.
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(importer_module, "main", import_data,
                                kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConvertCoverOp) {
  constexpr std::string_view program = R"(
fn main(x: u32, y: u32) {
  let foo = x == y;
  cover!("x_equals_y", foo)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConvertCoverOpWithConditionalGuard) {
  constexpr std::string_view program = R"(
fn f(x: u32) -> u32 {
    cover!("x_less_than_0", x < u32:0);
    x
}

fn main(y: u32) -> u32 {
    if y < u32:10 {
        f(y)
    } else {
        u32:0
    }
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConvertAssertOpWithConditionalGuard) {
  constexpr std::string_view program = R"(
fn f(x: u32) -> u32 {
    assert!(x < u32:5, "x_less_than_5");
    x
}

fn main(y: u32) -> u32 {
    if y < u32:10 {
        f(y)
    } else {
        u32:0
    }
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConvertGateOp) {
  constexpr std::string_view program = R"(
fn main(p: bool, x: u32) -> u32 {
  gate!(p, x)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConvertRangeOpUnsigned) {
  constexpr std::string_view program = R"(
fn main() -> u32[5] {
  u32:2..u32:7
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConvertRangeOpSigned) {
  constexpr std::string_view program = R"(
fn main() -> s32[4] {
  s32:-2..s32:2
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConvertRangeOpUnsignedInclusiveEnd) {
  constexpr std::string_view program = R"(
fn main() -> u4[16] {
  u4:0..=u4:15
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConvertRangeOpSignedInclusiveEnd) {
  constexpr std::string_view program = R"(
fn main() -> s4[16] {
  s4:-8..=s4:7
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConvertRangeOpInclusiveEndSameValue) {
  constexpr std::string_view program = R"(
fn main() -> s32[1] {
  s32:0x80000000..=s32:0x80000000
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, PublicFnGetsTokenWrapper) {
  constexpr std::string_view program = R"(
fn callee_callee(x:u32) -> u32 {
  fail!("failure", x > u32:3);
  x
}

pub fn main(x:u32) -> u32 {
  callee_callee(x)
}

fn callee(x:u32) -> u32 {
  main(x)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, NonpublicFnDoesNotGetTokenWrapper) {
  constexpr std::string_view program = R"(
fn callee_callee(x:u32) -> u32 {
  fail!("failure", x > u32:3);
  x
}

fn main(x:u32) -> u32 {
  callee_callee(x)
}

fn callee(x:u32) -> u32 {
  main(x)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, HandlesChannelDecls) {
  constexpr std::string_view program = R"(
proc main {
  init { () }
  config() {
    let (p0, c0) : (chan<u32> out, chan<u32> in) = chan<u32>("u32_chan");
    let (p1, c1) : (chan<u64> out, chan<u64> in) = chan<u64>("u64_chan");
    let (p2, c2) : (chan<(u64, (u64, (u64)))> out, chan<(u64, (u64, (u64)))> in) = chan<(u64, (u64, (u64)))>("tuple_chan");
    let (p3, c3) = chan<(u64, (u64, u64[4]))>("tuple_with_array_chan");
  }

  next(state: ()) {
    ()
  }
}
)";

  ConvertOptions options;
  options.emit_positions = false;
  // This test won't pass with verify_ir set to true
  options.verify_ir = false;
  options.lower_to_proc_scoped_channels = false;
  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "main", import_data, options));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, HandlesBasicProc) {
  constexpr std::string_view program = R"(
proc producer {
  c: chan<u32> out;
  init { u32:1 }
  config(output_c: chan<u32> out) {
    (output_c,)
  }
  next(i: u32) {
    let tok = send(join(), c, i);
    i + u32:2
  }
}

proc consumer {
  c: chan<u32> in;
  init { u32:3 }
  config(input_c: chan<u32> in) {
    (input_c,)
  }
  next(i: u32) {
    let (tok, i) = recv(join(), c);
    i + i
  }
}

proc main {
  init { () }
  config() {
    let (p, c) = chan<u32>("my_chan");
    spawn producer(p);
    spawn consumer(c);
    ()
  }
  next(state: ()) { () }
}
)";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "main", import_data, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, HandlesBasicProcAlias) {
  constexpr std::string_view program = R"(
proc Foo {
  c: chan<u32> out;
  init { u32:1 }
  config(output_c: chan<u32> out) {
    (output_c,)
  }
  next(i: u32) {
    let tok = send(join(), c, i);
    i + u32:2
  }
}

pub proc FooAlias = Foo;
)";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "FooAlias", import_data,
                                kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, HandlesParametricProcAlias) {
  constexpr std::string_view program = R"(
proc Foo<N: u32> {
  c: chan<uN[N]> out;
  init { uN[N]:1 }
  config(output_c: chan<uN[N]> out) {
    (output_c,)
  }
  next(i: uN[N]) {
    let tok = send(join(), c, i);
    i + uN[N]:2
  }
}

pub proc FooAlias = Foo<16>;
)";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "FooAlias", import_data,
                                kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, HandlesParametricProcAliasCallingParametricFn) {
  constexpr std::string_view program = R"(
fn bar<Y: u32>(i: uN[Y]) -> uN[Y] {
  i+i
}

proc Foo<N: u32> {
  c: chan<uN[N]> out;
  init { uN[N]:1 }
  config(output_c: chan<uN[N]> out) {
    (output_c,)
  }
  next(i: uN[N]) {
    let result = bar<N>(i);
    let tok = send(join(), c, result);
    result + uN[N]:1
  }
}

pub proc FooAlias = Foo<16>;
)";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "FooAlias", import_data,
                                kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, HandlesProcAliasToImportedProc) {
  ImportData import_data = CreateImportDataForTest();

  constexpr std::string_view imported = R"(
pub proc Foo<N: u32> {
  c: chan<uN[N]> out;
  init { uN[N]:1 }
  config(output_c: chan<uN[N]> out) {
    (output_c,)
  }
  next(i: uN[N]) {
    let tok = send(join(), c, i);
    i + uN[N]:2
  }
}
  )";
  XLS_EXPECT_OK(
      ParseAndTypecheck(imported, "imported.x", "imported", &import_data));

  constexpr std::string_view program = R"(
import imported;
pub proc FooAlias = imported::Foo<16>;
  )";
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "FooAlias", import_data,
                                kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, HandlesProcWithTypeAlias) {
  constexpr std::string_view program = R"(
proc P {
    type MyU32 = u32;

    s: chan<MyU32> out;

    config(s: chan<MyU32> out) { (s,) }

    init { MyU32:42 }

    next(state: MyU32) {
        send(join(), s, state);
        let new_state = state + MyU32:1;
        new_state
    }
}
)";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "P", import_data, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, HandlesProcWithMultipleSpawn) {
  constexpr std::string_view program = R"(
proc C {
    s: chan<u32> in;

    config(s: chan<u32> in) {
      (s,)
    }

    init { u32:0 }

    next(state: u32) {
      let (tok, data) = recv(join(), s);

       state + data
    }
}

proc B {
    s: chan<u32> in;
    s0_out: chan<u32> out;
    config(s: chan<u32> in) {
      let (s0_out, s0_in)  = chan<u32>("s0");

      spawn C(s0_in);

      (s, s0_out)
    }

    init { u32:0 }

    next(state: u32) {
      let (tok, data) = recv(join(), s);
      send(tok, s0_out, data);

      state + data
    }
}

proc A {
    s: chan<u32> in;
    s0_out: chan<u32> out;
    s1_out: chan<u32> out;

    config(s: chan<u32> in) {
        let (s0_out, s0_in)  = chan<u32>("s0");
        let (s1_out, s1_in)  = chan<u32>("s1");

        spawn B(s0_in);
        spawn B(s1_in);
        (s, s0_out, s1_out)
    }

    init {  }

    next(state: ()) {
        let (tok, data) = recv(join(), s);
        send(tok, s0_out, data);
        send(tok, s1_out, data);
    }
}
)";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "A", import_data, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, SendIfRecvIf) {
  constexpr std::string_view program = R"(proc producer {
  c: chan<u32> out;

  init {
    false
  }

  config(c: chan<u32> out) {
    (c,)
  }

  next(do_send: bool) {
    send_if(join(), c, do_send, ((do_send) as u32));
    !do_send
  }
}

proc consumer {
  c: chan<u32> in;

  init {
    false
  }

  config(c: chan<u32> in) {
    (c,)
  }

  next(do_recv: bool) {
    let (_, foo) = recv_if(join(), c, do_recv, u32:42);
    !do_recv
  }
}

proc main {
  init { () }
  config() {
    let (p, c) = chan<u32>("my_chan");
    spawn producer(p);
    spawn consumer(c);
    ()
  }
  next(state: ()) { () }
})";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "main", import_data, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, Join) {
  constexpr std::string_view program = R"(proc foo {
  p0: chan<u32> out;
  p1: chan<u32> out;
  p2: chan<u32> out;
  c3: chan<u32> in;

  init {
    u32:0
  }

  config() {
    let (p0, c0) = chan<u32>("chan0");
    let (p1, c1) = chan<u32>("chan1");
    let (p2, c2) = chan<u32>("chan2");
    let (p3, c3) = chan<u32>("chan3");
    (p0, p1, p2, c3)
  }

  next(state: u32) {
    let tok = join();
    let tok0 = send(tok, p0, ((state) as u32));
    let tok1 = send(tok, p1, ((state) as u32));
    let tok2 = send(tok, p2, ((state) as u32));
    let tok3 = send(tok0, p0, ((state) as u32));
    let tok = join(tok0, tok1, tok2, send(tok0, p0, state as u32));
    let (tok, value) = recv(tok3, c3);
    state + u32:1
  }
}

proc main {
  init { () }
  config() {
    let (p, c) = chan<u32>("my_chan");
    spawn foo();
  }
  next(state: ()) { () }
}
)";

  ConvertOptions options;
  options.emit_positions = false;
  // This test won't pass with verify_ir set to true
  options.verify_ir = false;
  options.lower_to_proc_scoped_channels = false;
  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "main", import_data, options));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, BoundaryChannels) {
  constexpr std::string_view program = R"(proc foo {
  in_0: chan<u32> in;
  in_1: chan<u32> in;
  output: chan<u32> out;

  init { () }

  config(in_0: chan<u32> in, in_1: chan<u32> in, output: chan<u32> out) {
    (in_0, in_1, output)
  }

  next(state: ()) {
    let (tok, a) = recv(join(), in_0);
    let (tok, b) = recv(tok, in_1);
    let tok = send(tok, output, a + b);
    ()
  }
})";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "foo", import_data, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, PassChannelAcrossMultipleSpawns) {
  constexpr std::string_view program = R"(
  proc SomeProc {
    input: chan<u32> in;
    init { () }
    config(input: chan<u32> in) {
      (input,)
    }
    next(state: ()) {
      let (_, v) = recv(token(), input);
      trace_fmt!("recv: {}", v);
      state
    }
  }

  proc SomeOtherProc {
    init { () }
    config(input: chan<u32> in) {
      spawn SomeProc(input);
    }
    next(state: ()) {
      ()
    }
  }

  proc YetAnotherProc {
    output: chan<u32> out;
    init { () }
    config() {
      let (output, input) = chan<u32>("in_out");
      spawn SomeOtherProc(input);
      (output,)
    }
    next(state: ()) {
      send(token(), output, u32:0);
      state
    }
  }
  )";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "YetAnotherProc", import_data,
                                kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ReceiveFromBoundaryChannelArrayElement) {
  constexpr std::string_view program = R"(
  proc SomeProc {
    some_chan_array: chan<u32>[2] in;

    config(some_chan_array: chan<u32>[2] in) {
        (
            some_chan_array,
        )
    }

    init {  }

    next(state: ()) {
        let some_tok = token();
        let (tok_0, _) = recv(some_tok, some_chan_array[0]);
        let (tok_1, _) = recv(some_tok, some_chan_array[1]);
        join(tok_0, tok_1);
    }
  }
  )";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "SomeProc", import_data,
                                kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, DealOutChannelSubarray) {
  constexpr std::string_view program = R"(
  proc B {
    outs: chan<u32>[2] out;
    ins: chan<u32>[2] in;

    init {}

    config(outs: chan<u32>[2] out, ins: chan<u32>[2] in) {
      (outs, ins)
    }

    next(state: ()) {
      unroll_for!(j, tok) : (u32, token) in u32:0..u32:2 {
        let tok = send(tok, outs[j], j);
        let(tok, _) = recv(tok, ins[j]);
        tok
      }(join());
    }
  }

  proc A {
    init {}

    config() {
      let (outs, ins) = chan<u32>[2][2]("the_channel");
      unroll_for!(i, _) : (u32, ()) in u32:0..u32:2 {
        spawn B(outs[i], ins[i]);
      }(());
    }

    next(state: ()) { state }
  }
  )";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "A", import_data, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, DealOutBoundaryChannelSubarray) {
  constexpr std::string_view program = R"(
  proc B {
    outs: chan<u32>[2] out;
    ins: chan<u32>[2] in;

    init {}

    config(outs: chan<u32>[2] out, ins: chan<u32>[2] in) {
      (outs, ins)
    }

    next(state: ()) {
      unroll_for!(j, tok) : (u32, token) in u32:0..u32:2 {
        let tok = send(tok, outs[j], j);
        let(tok, _) = recv(tok, ins[j]);
        tok
      }(join());
    }
  }

  proc A {
    init {}

    config(outs : chan<u32>[2][2] out, ins: chan<u32>[2][2] in) {
      unroll_for!(i, _) : (u32, ()) in u32:0..u32:2 {
        spawn B(outs[i], ins[i]);
      }(());
    }

    next(state: ()) { state }
  }
  )";

  auto import_data = CreateImportDataForTest();
  XLS_EXPECT_OK(
      ConvertOneFunctionForTest(program, "A", import_data, kNoPosOptions));
}

TEST_F(IrConverterLegacyTest, LetChannelSubarrayInConfig) {
  constexpr std::string_view program = R"(
 proc B {
    outs: chan<u32>[2] out;
    ins: chan<u32>[2] in;

    init {}

    config(outs: chan<u32>[2] out, ins: chan<u32>[2] in) {
      (outs, ins)
    }

    next(state: ()) {
      unroll_for!(j, tok) : (u32, token) in u32:0..u32:2 {
        let tok = send(tok, outs[j], j);
        let(tok, _) = recv(tok, ins[j]);
        tok
      }(join());
    }
  }

  proc A {
    init {}

    config() {
      let (outs, ins) = chan<u32>[2][2]("the_channel");
      let outs0 = outs[0];
      let ins0 = ins[0];
      let outs1 = outs[1];
      let ins1 = ins[1];
      spawn B(outs0, ins0);
      spawn B(outs1, ins1);
    }

    next(state: ()) { state }
  }
  )";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "A", import_data, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, LetChannelSubarrayInNext) {
  constexpr std::string_view program = R"(
  proc A {
    outs: chan<u32>[2][2] out;
    ins: chan<u32>[2][2] in;

    init {}

    config() {
      let (outs, ins) = chan<u32>[2][2]("the_channel");
      (outs, ins)
    }

    next(state: ()) {
      let (outs0, ins0) = (outs[0], ins[0]);
      let (outs1, ins1) = (outs[1], ins[1]);
      unroll_for!(j, tok) : (u32, token) in u32:0..u32:2 {
        let tok = send(tok, outs0[j], j);
        let(tok, _) = recv(tok, ins0[j]);
        let tok = send(tok, outs1[j], j);
        let(tok, _) = recv(tok, ins1[j]);
        tok
      }(join());
    }
  }
  )";

  auto import_data = CreateImportDataForTest();
  EXPECT_THAT(
      ConvertOneFunctionForTest(program, "A", import_data, kNoPosOptions),
      StatusIs(absl::StatusCode::kInvalidArgument,
               HasSubstr("Invalid channel subarray use")));
}

TEST_F(IrConverterLegacyTest, TopProcWithState) {
  constexpr std::string_view program = R"(
proc main {
  init {
    (u32:4, u32[4]:[u32:4000, u32:4001, u32:36, ...])
  }
  config() { () }

  next(state: (u32, u32[4])) { state }
})";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "main", import_data, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, FormatMacro) {
  constexpr std::string_view program = R"(fn main() {
  trace_fmt!("Look! I don't explode!");
})";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "main", import_data, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, FormatMacroStructArg) {
  constexpr std::string_view program = R"(
struct Point {
  x: u32,
  y: s8,
}

fn main() {
  let p = Point{x: u32:42, y: s8:7};
  trace_fmt!("Look! I don't explode *and* I can trace a struct: {}", p);
})";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "main", import_data, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, FormatMacroNestedStructArg) {
  constexpr std::string_view program = R"(
struct U32Wrapper {
  v: u32
}

struct Point {
  x: U32Wrapper,
  y: s8,
}

fn main() {
  let p = Point{x: U32Wrapper{v: u32:42}, y: s8:7};
  trace_fmt!("Look! I don't explode *and* I can trace a nested struct: {}", p);
})";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "main", import_data, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, AssertFmt) {
  constexpr std::string_view program = R"(
fn main(x: u32) -> u32 {
  const VALUE = u32:42;
  assert_fmt!(x != VALUE, "x_is_not_{}", VALUE);
  x
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  EXPECT_THAT(converted, HasSubstr("assert"));
  EXPECT_THAT(converted, HasSubstr("x_is_not_42"));
}

TEST_F(IrConverterLegacyTest, ParameterShadowingModuleLevelConstant) {
  constexpr std::string_view program = R"(
  const FOO = u32:0;

  fn test1<FOO:u32>(x:u32) -> u32 {
    x + FOO
  }

  fn test2<FOO:u32>(x:u32) -> u32 {
    test1<FOO>(x) - FOO
  }

  fn main() -> u32 {
    let foo = test2<u32:3>(u32:3);
    foo
  }
  )";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(program, kNoPosOptions, &import_data));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, InvalidChannelDecl) {
  constexpr std::string_view program = R"(fn main() {
  let _ = chan<u8>("my_chan");
  ()
  })";

  auto import_data = CreateImportDataForTest();
  EXPECT_THAT(
      ConvertOneFunctionForTest(program, "main", import_data, kNoPosOptions),
      StatusIs(absl::StatusCode::kInternal,
               HasSubstr("Channels can only be declared in")));
}

TEST_F(IrConverterLegacyTest, InvalidSpawn) {
  constexpr std::string_view program = R"(
proc p {
  init { () }
  config() { () }
  next(state: ()) { state }
}

fn main() {
  spawn p();
}
)";

  auto import_data = CreateImportDataForTest();
  EXPECT_THAT(
      ConvertOneFunctionForTest(program, "main", import_data, kNoPosOptions),
      StatusIs(absl::StatusCode::kUnimplemented,
               HasSubstr("Functions cannot spawn procs.")));
}

TEST_F(IrConverterLegacyTest, InvalidSpawnProcScopedChannel) {
  constexpr std::string_view program = R"(
proc p {
  init { () }
  config() { () }
  next(state: ()) { state }
}

fn main() {
  spawn p();
}
)";
  auto import_data = CreateImportDataForTest();
  EXPECT_THAT(ConvertOneFunctionForTest(program, "main", import_data,
                                        kProcScopedChannelOptions),
              StatusIs(absl::StatusCode::kUnimplemented,
                       HasSubstr("Functions cannot spawn procs.")));
}

TEST_F(IrConverterLegacyTest, InvalidSpawnInNextProcScoped) {
  constexpr std::string_view program = R"(
proc p {
  init { () }
  config() { () }
  next(state: ()) { state }
}

proc main {
  init { () }
  config() { () }
  next(state: ()) {
    spawn p();
  }
}
)";
  auto import_data = CreateImportDataForTest();
  EXPECT_THAT(ConvertOneFunctionForTest(program, "main", import_data,
                                        kProcScopedChannelOptions),
              StatusIs(absl::StatusCode::kUnimplemented,
                       HasSubstr("Procs can only be spawned in a proc `config` "
                                 "method.")));
}

TEST_F(IrConverterLegacyTest, StringWithUnsignedRangeCharacter) {
  constexpr std::string_view program = R"(fn main() -> u8[1] {
  "\x80"  // -128 in signed char interpretation
})";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "main", import_data, kNoPosOptions));
  ExpectIr(converted);
}

// TODO(google/xls#917): Remove this test when empty arrays are supported.
TEST_F(IrConverterLegacyTest, EmptyArray) {
  constexpr std::string_view program = R"(
    fn main() -> u32[0] {
      u32[0]:[]
    }
)";

  auto import_data = CreateImportDataForTest();
  EXPECT_THAT(
      ConvertOneFunctionForTest(program, "main", import_data, kNoPosOptions),
      StatusIs(absl::StatusCode::kInvalidArgument,
               HasSubstr("Array u32[0]:[] was empty")));
}

TEST_F(IrConverterLegacyTest, TraceFmt) {
  constexpr std::string_view program = R"(
    fn trace_and_add(x: u32, y: u32[u32:2]) -> u32 {
      trace_fmt!("x = {}, y = {}", x, y);
      x + y[u8:1] + y[u8:0]
    }

    fn assert_trace_and_add(x: u32) -> u32 {
      if x == u32:5 { fail!("x_is_now_5", u32:0) } else { u32:0 };
      trace_and_add(x, [u32:4, u32:6])
    }

    proc main {
      c: chan<u32> out;
      init {
        u32:0
      }
      config(input_c: chan<u32> out) {
        (input_c,)
      }
      next(i: u32) {
        let tok = send(join(), c, i);
        assert_trace_and_add(i)
      }
    }
)";

  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(program, "main", import_data, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, TraceFmtSimpleNoArgs) {
  constexpr std::string_view program = R"(fn main() {
    trace_fmt!("Hello world!");
})";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, TraceFmtSimpleWithArgs) {
  constexpr std::string_view program = R"(fn main() {
    let foo = u32:2;
    trace_fmt!("Hello {} {:x}!", "world", foo + u32:1);
})";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, VTraceFmtSimpleNoArgs) {
  constexpr std::string_view program = R"(fn main() {
    vtrace_fmt!(u32:3, "Hello world!");
})";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, VTraceFmtSimpleWithArgs) {
  constexpr std::string_view program = R"(fn main() {
    const VERBOSITY = u32:4;
    let foo = u32:3;
    vtrace_fmt!(VERBOSITY + u32:1, "Hello {} {:x}!", "world", foo + u32:1);
})";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, WideningCastsUnErrors) {
  constexpr std::string_view program =
      R"(
fn main(x: u8) -> u32 {
  let x_32 = widening_cast<u32>(x);
  let x_4  = widening_cast<u4>(x_32);
  x_32 + widening_cast<u32>(x_4)
}
)";

  auto import_data = CreateImportDataForTest();
  EXPECT_THAT(
      ConvertOneFunctionForTest(program, "main", import_data, kNoPosOptions),
      StatusIs(absl::StatusCode::kInvalidArgument,
               HasSubstr("Cannot cast from type `uN[32]` (32 bits) "
                         "to `uN[4]` (4 bits) with widening_cast")));
}

TEST_F(IrConverterLegacyTest, WideningAndCheckedCasts) {
  constexpr std::string_view program =
      R"(
fn main(x: u8, y: s8) -> u32 {
  let x_32 = checked_cast<u32>(widening_cast<u32>(x));
  let y_16 = widening_cast<s16>(checked_cast<s7>(y));
  let y_times_two_32 = checked_cast<s32>(y_16) + widening_cast<s32>(y_16);
  x_32 + checked_cast<u32>(y_times_two_32)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ArrayOfTokenError) {
  constexpr std::string_view program =
      R"(
proc main {
  init {}
  config() {}
  next(st:()) {
    let x: token = [join()][u1:0];
    ()
  }
}
)";

  auto import_data = CreateImportDataForTest();
  EXPECT_THAT(
      ConvertOneFunctionForTest(program, "main", import_data, kNoPosOptions),
      StatusIs(absl::StatusCode::kInvalidArgument,
               HasSubstr("tokens cannot be placed in arrays.")));
}

TEST_F(IrConverterLegacyTest, ArrayOfTupleWithTokenError) {
  constexpr std::string_view program =
      R"(
proc main {
  init {}
  config() {}
  next(st:()) {
    let x: token = [(join(),)][u1:0];
    ()
  }
}
)";
  auto import_data = CreateImportDataForTest();
  EXPECT_THAT(
      ConvertOneFunctionForTest(program, "main", import_data, kNoPosOptions),
      StatusIs(absl::StatusCode::kInvalidArgument,
               HasSubstr("tokens cannot be placed in arrays.")));
}

TEST_F(IrConverterLegacyTest, ArraySizeBuiltin) {
  constexpr std::string_view program =
      R"(
fn main(x: u8[42]) -> u32 {
  array_size(x)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, InvokeFunctionWithTraceParametric) {
  constexpr std::string_view program =
      R"(
fn bar<C: u32>() {
  trace_fmt!("C is {}", C);
}

fn foo<A: u32>(x: u32, y: u32) -> u32 {
  bar<A>();
  x + y
}

fn main(x: u32, y: u32) -> u32 {
  foo<u32:1>(x, y)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, InvokeFunctionWithTrace) {
  constexpr std::string_view program =
      R"(
fn foo(x: u32, y: u32) -> u32 {
  trace_fmt!("x is {}", x);
  trace_fmt!("y is {}", y);
  x + y
}

fn main(x: u32, y: u32) -> u32 {
  foo(x, y)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ImplicitTokenFalseDoesntClobberTrue) {
  constexpr std::string_view program =
      R"(
fn foo(x: u32) -> u32 {
  trace_fmt!("x is {}", x);
  x
}

fn bar() {}

fn main() {
  bar();
  foo(u32:2);
  bar();
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, InvokeFunctionWithTraceInForLoop) {
  constexpr std::string_view program =
      R"(
fn foo(x: u32, y: u32) -> u32 {
  trace_fmt!("x is {}", x);
  trace_fmt!("y is {}", y);
  x + y
}

fn main(x: u32[4]) -> u32[4] {
  for (i, accum): (u32, u32[4]) in u32:0..u32:4 {
    update(x, i, foo(accum[i], x[i]))
  }(x)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, BitCount) {
  constexpr std::string_view program = R"(
struct S {
  a: u32
}

struct T<N: u32> {
  a: uN[N]
}

fn main() -> u32 {
  bit_count<u32>() +
  bit_count<s64>() +
  bit_count<u32[u32:4]>() +
  bit_count<bool>() +
  bit_count<S>() +
  bit_count<T<u32:4>>() +
  bit_count<(u32, bool)>()
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ElementCount) {
  constexpr std::string_view program = R"(
struct S {
  a: u32,
  b: u32
}

struct T<N: u32> {
  a: uN[N]
}

fn main() -> u32 {
  element_count<u32>() +
  element_count<s64>() +
  element_count<u32[u32:4]>() +
  element_count<u32[u32:4][u32:5]>() +
  element_count<bool>() +
  element_count<S>() +
  element_count<T<u32:4>>() +
  element_count<(u32, bool)>()
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConfiguredValue) {
  constexpr std::string_view program = R"(
enum MyEnum : u2 {
  A = 0,
  B = 1,
  C = 2,
}

fn main() -> (bool, u32, s32, MyEnum, bool, u32, s32, MyEnum) {
  let b_default = configured_value_or<bool>("b_default", false);
  let u_default = configured_value_or<u32>("u32_default", u32:42);
  let s_default = configured_value_or<s32>("s32_default", s32:-100);
  let e_default = configured_value_or<MyEnum>("enum_default", MyEnum::C);
  let b_override = configured_value_or<bool>("b_override", false);
  let u_override = configured_value_or<u32>("u32_override", u32:42);
  let s_override = configured_value_or<s32>("s32_override", s32:-100);
  let e_override = configured_value_or<MyEnum>("enum_override", MyEnum::C);
  (b_default, u_default, s_default, e_default, b_override, u_override, s_override, e_override)
}
)";
  ConvertOptions options;
  options.configured_values = {"b_override:true", "u32_override:123",
                               "s32_override:-200", "enum_override:MyEnum::B"};
  options.emit_positions = false;
  options.lower_to_proc_scoped_channels = false;
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, options));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MapInvocationWithBuiltinFunction) {
  constexpr std::string_view program =
      R"(
fn main(x: u32[4]) -> u32[4] {
  map(x, clz)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MapInvocationWithParametricFunction) {
  constexpr std::string_view program =
      R"(
fn f<N:u32, K:u32>(x: u32) -> uN[N] { x as uN[N] + K as uN[N] }

fn main() -> (u5[4], u6[4]) {
  (
    map(u32[4]:[0, 1, 2, 3], f<u32:5, u32:17>),
    map(u32[4]:[0, 1, 2, 3], f<u32:6, u32:3>),
  )
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest,
       MapInvocationWithParametricFunctionFromParametricFunction) {
  constexpr std::string_view program =
      R"(
fn f<N:u32>(x: u32) -> uN[N] { x as uN[N] }

fn g<X:u32>(x: u32) -> uN[X][3] { map([x, x + u32:1, x + u32:2], f<X>) }

fn main() -> (u5[3], u6[3]) {
  (
    g<u32:5>(u32:1),
    g<u32:6>(u32:2),
  )
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MapInvocationWithImplicitToken) {
  constexpr std::string_view program =
      R"(
fn f(x: u32) -> u64 {
    assert!(x != u32:42, "foobar");
    u16:0 ++ x ++ u16:4
}

fn main() -> u64[4] {
    map(u32[4]:[0, 1, 2, 3], f)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MapInvocationWithStruct) {
  constexpr std::string_view program =
      R"(
struct Foo { x: u32, y: u32 }
struct Bar { a: u16, b: u16, c: u32 }
fn f(x: Foo) -> Bar {
    assert!(x.x != u32:42, "foobar");
    Bar { a: x.x[0+:u16], b: x.x[16+:u16], c: x.y }
}

fn main(lst: Foo[u32:6]) -> Bar[u32:6] {
    map(lst, f)
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConvertFilesToPackageFailsTypeCheck) {
  constexpr std::string_view program =
      R"(
fn main() -> u8 {
  u32:0
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(xls::TempFile temp,
                           xls::TempFile::CreateWithContent(program, ".x"));
  const std::string dslx_str_path = temp.path().string();
  bool printed_error = false;
  absl::StatusOr<PackageConversionData> result =
      ConvertFilesToPackage({dslx_str_path},
                            /*stdlib_path=*/"", {temp.path()}, kNoPosOptions,
                            /*top=*/"main",
                            /*package_name=*/std::nullopt, &printed_error);
  EXPECT_THAT(result, StatusIs(absl::StatusCode::kInvalidArgument,
                               HasSizeMismatch("u32", "u8")));
}

TEST_F(IrConverterLegacyTest, ProcWithNonConstArgumentInConfigIsNotConverted) {
  constexpr std::string_view program =
      R"(
proc Generator {
  out_ch: chan<u32> out;
  val: u32;

  init {
    ()
  }

  config(out_ch: chan<u32> out, val: u32) {
    (out_ch, val)
  }

  next(state: ()) {
    send(join(), out_ch, val);
  }
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  // No actual code since no top procs have a convertible config
  EXPECT_EQ(converted, "package test_module\n");
}

TEST_F(IrConverterLegacyTest, ProcWithUnconvertibleConfigGivesUsefulError) {
  constexpr std::string_view program =
      R"(
proc Generator {
  out_ch: chan<u32> out;
  val: u32;

  init {
    ()
  }

  config(out_ch: chan<u32> out, val: u32, val2: u32) {
    (out_ch, val + val2)
  }

  next(state: ()) {
    send(join(), out_ch, val);
  }
}
)";

  EXPECT_THAT(
      ConvertOneFunctionForTest(program, "Generator", kNoPosOptions),
      StatusIs(absl::StatusCode::kInvalidArgument,
               HasSubstr("Cannot convert proc 'Generator' due to non-channel "
                         "config arguments: 'val: u32', 'val2: u32'")));
}

// TODO: https://github.com/google/xls/issues/2876 - TIv2 does not yet
// support "use" syntax.
TEST_F(IrConverterLegacyTest, DISABLED_UseTreeEntryCallInParametric) {
  constexpr std::string_view program = R"(
  #![feature(use_syntax)]
  use std::is_pow2;
  fn f<N: u32>(x: bits[N]) -> bool { is_pow2(x) }
  fn main() -> bool { f(u2:3) }
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MatchExhaustiveMultiplePatternLastArm) {
  constexpr std::string_view program = R"(
fn main(x: u2) -> u32 {
  match x {
    u2:0 | u2:1 => u32:0,
    u2:2 | u2:3 => u32:1,
  }
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MatchExhaustiveOneRangeAndValueInSingleArm) {
  constexpr std::string_view program = R"(
  fn main(x: u2) -> u32 {
    match x {
      u2:0..u2:3 | u2:3 => u32:42,
    }
  }
  )";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConvertImplFunctionOnStructMember) {
  constexpr std::string_view program = R"(
  struct F {}

  impl F {
    pub fn bar(self) -> F { F {} }
  }

  struct G { f: F }
  impl G {
    pub fn foo(self) -> F {
      self.f.bar()
    }
  }

  fn top_fn() -> F {
    let g = G { f: F {} };
    g.foo()
  }

  fn another_fn() -> G {
    G { f: F {} }
  }
  )";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ImplColonRef) {
  constexpr std::string_view program = R"(
  struct F {}

  impl F {
    fn bar() -> u32 { u32:0 }
  }

  fn bar() -> u32 { u32:0 }

  fn top_fn() -> u32 {
    F::bar()
  }
  )";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, SimpleImpl) {
  constexpr std::string_view program = R"(
  struct F {}

  impl F {
    pub fn bar() -> u32 { u32:0 }
  }

  fn top_fn() -> u32 {
    F::bar()
  }
  )";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, SimpleImplMethod) {
  constexpr std::string_view program = R"(
  struct F {}

  impl F {
    pub fn bar(self) -> F { self }
  }

  fn top_fn() -> F {
    let f = F {};
    f.bar()
  }
  )";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MatchExhaustiveRangeInTrailingArm) {
  constexpr std::string_view program = R"(
  fn main(x: u2) -> u32 {
    match x {
      u2:3 => u32:42,
      u2:0..u2:3 => u32:64,
    }
  }
  )";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, MatchRangeInclusiveEnd) {
  constexpr std::string_view program = R"(
fn main(x: u2) -> u32 {
match x {
 u2:0..=u2:3 => u32:1,
}
}
)";

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ChannelAttributes) {
  constexpr std::string_view program = R"(#![feature(channel_attributes)]
proc producer {
  c: chan<u32> out;
  init {
    u32:0
  }
  config(input_c: chan<u32> out) {
    (input_c,)
  }
  next(i: u32) {
    let tok = send(join(), c, i);
    i + u32:1
  }
}

proc consumer {
  c: chan<u32> in;
  init {
    u32:0
  }
  config(input_c: chan<u32> in) {
    (input_c,)
  }
  next(i: u32) {
    let (tok, i) = recv(join(), c);
    i + i
  }
}

proc main {
  init { () }
  config() {
    let (p, c) =
    #[channel(depth=0)]
    chan<u32>("my_chan0");
    spawn producer(p);
    spawn consumer(c);

    let (p, c) =
    #[channel(depth=1, register_push_outputs=true, register_pop_outputs=true, bypass=false)]
    chan<u32>("my_chan1");
    spawn producer(p);
    spawn consumer(c);

    let (p, c) =
    #[channel(depth=0, input_flop_kind=zero_latency, output_flop_kind=flop)]
    chan<u32>("my_chan2");
    spawn producer(p);
    spawn consumer(c);
    ()
  }
  next(state: ()) { () }
}
)";
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(program, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ProcMemberInDynamicLoopUnsupported) {
  constexpr std::string_view program = R"(
proc Proc {
  inputs: chan<s32>[2] in;
  outputs: chan<s32>[2] out;
  config() {
    let (a, b) = chan<s32>[2]("c");
    (b, a)
  }
  init { () }
  next(state: ()) {
    let tok = join();
    for (i, _) in u32:0..u32:2 {
        recv(tok, inputs[i]);
        send(tok, outputs[i], s32:1);
    } (());
  }
}
)";
  EXPECT_THAT(
      ConvertModuleForTest(program),
      StatusIs(
          absl::StatusCode::kUnimplemented,
          HasSubstr(
              "Accessing proc member in non-unrolled loop is unsupported")));
}

TEST_F(IrConverterLegacyTest, GenericTypePassthroughFunction) {
  if (TypeInferenceVersion::kVersion2 == TypeInferenceVersion::kVersion1) {
    return;
  }

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(R"(
#![feature(generics)]

struct S { a: u32 }

fn foo<T: type>(a: T) -> T { a }

type MyInt = u34;

enum MyEnum { A = 0 }

fn main() {
  const C = foo<u32>(5);
  const D = foo<u16[3]>([1, 2, 3]);
  const E = foo((s8:5, s8:6));
  const F = foo<S>(S { a: 5 });
  const G = foo<MyInt>(10);
  const H = foo(MyEnum::A);
}
)",
                                                kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, SquareAnythingFunction) {
  if (TypeInferenceVersion::kVersion2 == TypeInferenceVersion::kVersion1) {
    return;
  }

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(R"(
#![feature(generics)]

fn square<T: type>(a: T) -> T { a * a }

fn main() -> (u32, s64) {
  (square(u32:5), square<s64>(-12))
}
)",
                                                kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, OptionalStruct) {
  if (TypeInferenceVersion::kVersion2 == TypeInferenceVersion::kVersion1) {
    return;
  }

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(R"(
#![feature(generics)]

struct Optional<T: type> {
  has_value: bool,
  value: T
}

fn make_optional<T: type>(value: T) -> Optional<T> {
  Optional<T> { has_value: true, value: value }
}

fn make_nullopt<T: type>() -> Optional<T> {
  zero!<Optional<T>>()
}

fn main() -> (Optional<u32>, Optional<u32>, Optional<u32[3]>,
              Optional<Optional<u32>>) {
  (make_optional(u32:5),
   make_nullopt<u32>(),
   make_optional<u32[3]>([1, 2, 3]),
   make_optional(make_optional(u32:5)))
}
)",
                                                kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, CallFooOnAnything) {
  if (TypeInferenceVersion::kVersion2 == TypeInferenceVersion::kVersion1) {
    return;
  }

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(R"(
#![feature(generics)]

struct U32Wrapper { a: u32 }
struct S64Wrapper { a: s64 }

impl U32Wrapper {
  fn foo(self) -> u32 { self.a + 5 }
}

impl S64Wrapper {
  fn foo(self) -> s64 { self.a * self.a }
}

fn call_foo<R: type, T: type>(value: T) -> R {
  value.foo()
}

fn main() -> (u32, s64) {
  (call_foo<u32>(U32Wrapper { a: 10 }), call_foo<s64>(S64Wrapper { a: -2 }))
}
)",
                                                kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ToBits) {
  if (TypeInferenceVersion::kVersion2 == TypeInferenceVersion::kVersion1) {
    return;
  }

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(R"(
enum E : u16 {
  X = 1
}

#[derive(ToBits)]
struct Bar {
  data: u32
}

struct Baz {
  x: u24,
  y: u24
}

impl Baz {
  fn to_bits(self) -> u48 { self.y ++ self.x }
}

#[derive(ToBits)]
struct Foo {
  a: u32,
  b: s64,
  c: u8[3],
  d: (u8, u8),
  e: E,
  f: Bar,
  g: Baz
}

fn main() -> bits[bit_count<Foo>()] {
  let f = Foo {
    a: 5,
    b: -1,
    c: [10, 11, 12],
    d: (1, 2),
    e: E::X,
    f: Bar { data: 100 },
    g: Baz { x: 1, y: 2 }
  };

  f.to_bits()
}
)",
                                                kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, GenericProc) {
  if (TypeInferenceVersion::kVersion2 == TypeInferenceVersion::kVersion1) {
    return;
  }

  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(R"(
#![feature(generics)]

proc P<T: type> {
  c: chan<T> out;

  init {
    zero!<T>()
  }
  config(input_c: chan<T> out) {
    (input_c,)
  }
  next(i: T) {
    let tok = send(join(), c, i);
    i + 1
  }
}

proc Main {
  in_c_u32: chan<u32> in;
  in_c_s64: chan<s64> in;

  init {
    (u32:0, s64:0)
  }
  config() {
    let (out_c_u32, in_c_u32) = chan<u32>("c_u32");
    let (out_c_s64, in_c_s64) = chan<s64>("c_s64");
    spawn P<u32>(out_c_u32);
    spawn P<s64>(out_c_s64);
    (in_c_u32, in_c_s64)
  }
  next(i: (u32, s64)) {
    let (tok, i_u32) = recv(join(), in_c_u32);
    let (tok, i_s64) = recv(tok, in_c_s64);
    (i_u32, i_s64)
  }
}
)",
                                                kNoPosOptions));
  ExpectIr(converted);
}

constexpr std::string_view kPassChannelArraysAcrossSpawns = R"(
  proc SomeProc<N: u32> {
    ins: chan<u32>[N] in;
    init { () }
    config(ins: chan<u32>[N] in) {
      (ins,)
    }
    next(state: ()) {
      unroll_for! (i, _): (u32, ()) in u32:0..u32:4 {
        let (_, v) = recv(token(), ins[i]);
        trace_fmt!("recv: {}", v);
      }(());
      state
    }
  }

  proc SomeOtherProc<N: u32> {
    init { () }
    config(ins: chan<u32>[N] in) {
      spawn SomeProc<N>(ins);
    }
    next(state: ()) { () }
  }

  proc YetAnotherProc {
    outs: chan<u32>[4] out;
    init { () }
    config() {
      let (outs, ins) = chan<u32>[4]("ins_outs");
      spawn SomeOtherProc<u32:4>(ins);
      (outs,)
    }
    next(state: ()) {
      unroll_for! (i, _): (u32, ()) in u32:0..u32:4 {
        send(token(), outs[i], i);
      }(());
      state
    }
  }
)";

TEST_F(IrConverterLegacyTest, PassChannelArraysAcrossMultipleSpawns) {
  auto import_data = CreateImportDataForTest();
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(
          kPassChannelArraysAcrossSpawns, "YetAnotherProc", import_data,
          ConvertOptions{.emit_positions = false,
                         .verify_ir = false,
                         .lower_to_proc_scoped_channels = false}));

  ExpectIr(converted);
}

constexpr std::string_view kInvokeParametricFunctionInFuncAndProc =
    R"(
fn square<IMPL: bool>(x:u32) -> u32 {
  x * x
}

fn square_zero() -> u32 {
  square<false>(u32:0)
}

proc Counter {
  in_ch: chan<u32> in;
  out_ch: chan<u32> out;

  init {
    u32:0
  }

  config(in_ch: chan<u32> in, out_ch: chan<u32> out) {
    (in_ch, out_ch)
  }

  next(state: u32) {
    let (tok, in_data) = recv(join(), in_ch);
    let x = square<false>(in_data);
    let next_state = state + x;
    let tok = send(tok, out_ch, next_state);

    next_state
  }
}
)";

TEST_F(IrConverterLegacyTest, InvokeParametricFunctionInBothFuncAndProc) {
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(kInvokeParametricFunctionInFuncAndProc,
                           kNoPosOptions));
  ExpectIr(converted);
}

constexpr std::string_view kParametricDefaultClog2 = R"(
import std;

struct Foo <X: u32, Y: u32 = {std::clog2(X)}> {
    a: uN[X],
    b: uN[Y],
}

fn make_zero_foo<X: u32>() -> Foo<X> {
  zero!<Foo<X>>()
}

fn test() -> Foo<u32:5> {
 make_zero_foo<u32:5>()
}
)";
TEST_F(IrConverterLegacyTest, ParametricDefaultClog2InStruct) {
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(kParametricDefaultClog2, kNoPosOptions));
  ExpectIr(converted);
}

constexpr std::string_view kParametricDefaultBuiltin = R"(
struct Foo <X: u32, Y: u32 = {clz(X)}> {
    a: uN[X],
    b: uN[Y],
}

fn make_zero_foo<X: u32>() -> Foo<X> {
  zero!<Foo<X>>()
}

fn test() -> Foo<u32:5> {
 make_zero_foo<u32:5>()
}
)";

TEST_F(IrConverterLegacyTest, ParametricDefaultBuiltinInStruct) {
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(kParametricDefaultBuiltin, kNoPosOptions));
  ExpectIr(converted);
}

constexpr std::string_view kMemoryProc = R"(
pub type MemWord = u32;
pub struct MemReq {
    is_write: bool,
    address: u32,
    wdata: MemWord,
}

pub const MEM_SIZE = u32:4;
type State = MemWord[MEM_SIZE];

const DEFAULT_VALUE = u32:0x12345678 as MemWord;

pub proc Memory {
    req_in: chan<MemReq> in;
    data_out: chan<u32> out;

    config(req_in: chan<MemReq> in, data_out: chan<u32> out) { (req_in, data_out) }

    init { State:[DEFAULT_VALUE, ...] }

    next(state: State) {
        let (tok, req) = recv(join(), req_in);
        let state = if req.is_write { update(state, req.address, req.wdata) } else { state };

        // A read request returns a response.
        send_if(tok, data_out, !req.is_write, state[req.address]);
        state
    }
}
)";

TEST_F(IrConverterLegacyTest, ProcWithIndex) {
  XLS_ASSERT_OK_AND_ASSIGN(std::string converted,
                           ConvertModuleForTest(kMemoryProc, kNoPosOptions));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConvertWithoutTests) {
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(
          kProgramToVerifyTestConversion,
          ConvertOptions{.emit_positions = false,
                         .convert_tests = false,
                         .lower_to_proc_scoped_channels = false}));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, ConvertWithTests) {
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertModuleForTest(
          kProgramToVerifyTestConversion,
          ConvertOptions{.emit_positions = false,
                         .convert_tests = true,
                         .lower_to_proc_scoped_channels = false}));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, UtilityFunctionIncorrectUse) {
  constexpr std::string_view program = R"(
#[cfg(test)]
fn utility() { trace_fmt!("Test utility function"); }

fn func() {
  trace_fmt!("Regular function");
  utility();
}

#[test]
fn test_func() {
  func();
}
)";
  EXPECT_THAT(
      ConvertModuleForTest(
          program, ConvertOptions{.emit_positions = false,
                                  .convert_tests = false,
                                  .lower_to_proc_scoped_channels = false}),
      StatusIs(absl::StatusCode::kInvalidArgument,
               HasSubstr("Tried to convert a function 'utility' used in tests, "
                         "but test conversion is disabled")));
}

TEST_F(IrConverterLegacyTest, NonSynthNoAssert) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let x = u32:42;
  assert!(x > u32:11, "bang");
  x
})";
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(
          program, "f",
          ConvertOptions{.emit_assert = false,
                         .lower_to_proc_scoped_channels = false}));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, NonSynthNoTrace) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let x = u32:42;
  trace_fmt!("bang {}", x);
  x
})";
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(
          program, "f",
          ConvertOptions{.emit_trace = false,
                         .lower_to_proc_scoped_channels = false}));
  ExpectIr(converted);
}

TEST_F(IrConverterLegacyTest, NonSynthNoCover) {
  constexpr std::string_view program =
      R"(fn f() -> u32 {
  let x = u32:42;
  cover!("bang", x > u32:11);
  x
})";
  XLS_ASSERT_OK_AND_ASSIGN(
      std::string converted,
      ConvertOneFunctionForTest(
          program, "f",
          ConvertOptions{.emit_cover = false,
                         .lower_to_proc_scoped_channels = false}));
  ExpectIr(converted);
}
}  // namespace
}  // namespace xls::dslx
